#ifndef DBMS_H
#define DBMS_H

#include "record.h"
#include "table.h"
#include "node.h"
#include <cstring>
#include <string>
#include <vector>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>

using namespace std;

class DBMS {
private:
	string dbName;
	vector<TableManager> tableManagers;

	vector<string> getDatabases() {
		vector<string> ret;

		DIR* dir = opendir(".");
		dirent* ptr;
		while ((ptr = readdir(dir)) != 0) {
			if ((int)(ptr->d_type) == 4) {
				string name = (string)(ptr->d_name);
				if (Str::getFileExtensions(name) == "db") {
					ret.push_back(name);
				}
			}
		}

		return ret;
	}

	vector<string> getTables(string dbName) {
		vector<string> ret;

		DIR* dir = opendir(dbName.c_str());
		dirent* ptr;
		while ((ptr = readdir(dir)) != 0) {
			if ((int)(ptr->d_type) == 8) {
				string name = (string)(ptr->d_name);
				ret.push_back(name);
			}
		}

		return ret;
	}

	bool existDatabase(string dbName) {
		vector<string> dbs = getDatabases();

		for (int i = 0; i < dbs.size(); i++) {
			if (dbs[i] == dbName) {
				return true;
			}
		}

		return false;
	}

	bool existTable(string tableName) {
		vector<string> tables = getTables(dbName);

		for (int i = 0; i < tables.size(); i++) {
			if (tables[i] == tableName) {
				return true;
			}
		}

		return false;
	}

	int isDir(const char* filename) {
		struct stat buf;
		int ret = stat(filename, &buf);
		if(0 == ret) {
			if(buf.st_mode & S_IFDIR) {
				return 0;
			} else {
				return 1;
			}
		}
		return -1;
	}

	int deleteDir(const char* dirname) {
		char chBuf[256];
		DIR* dir = NULL;
	 	dirent *ptr;
		int ret = 0;
		dir = opendir(dirname);
		if(dir == NULL) {
			return -1;
		}
		while((ptr = readdir(dir)) != NULL) {
			ret = strcmp(ptr->d_name, ".");
			if(ret == 0) {
				continue;
			}
			ret = strcmp(ptr->d_name, "..");
			if(ret == 0) {
				continue;
			}
			snprintf(chBuf, 256, "%s/%s", dirname, ptr->d_name);
			ret = isDir(chBuf);
			if(ret == 0) {
				ret = deleteDir(chBuf);
				if(0 != ret) {
					return -1;
				}
			} else if(1 == ret) {
				ret = remove(chBuf);
				if(0 != ret) {
					return -1;
				}
			}
		}
		closedir(dir);

		ret = remove(dirname);
	    if(ret != 0) {
			return -1;
		}
		return 0;
	}

public:
	DBMS() {

	}

	~DBMS() {

	}

	bool createDatabase(string dbName) {
		if (existDatabase(dbName) == false) {
			mkdir(dbName.c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRWXG | S_IRWXO);
			return true;
		}

		cout << "Database already exist in DBMS::createDatabase" << endl;
		return false;
	}

	bool useDatabase(string dbName) {
		if (existDatabase(this->dbName)) {
			closeDatabase();
		}

		if (existDatabase(dbName)) {
			this->dbName = dbName;
			vector<string> tables = getTables(dbName);
			for (int i = 0; i < tables.size(); i++) {
				TableManager tableManager(dbName);
				tableManager.openTable(tables[i]);
				tableManagers.push_back(tableManager);
			}
			return true;
		}

		cout << "Database not found error in DBMS::useDatabase" << endl;
		return false;
	}

	bool closeDatabase() {
		if (dbName != "") {
			for (int i = 0; i < tableManagers.size(); i++) {
				tableManagers[i].closeTable();
			}

			dbName = "";
			tableManagers.clear();
			return true;
		}

		cout << "No database to close in DBMS::closeDatabase" << endl;
		return false;
	}

	bool dropDatabase(string dbName) {
		//delete database(the directory)

		if (existDatabase(dbName)) {
			deleteDir(dbName.c_str());
			return true;
		}

		cout << "Database not found error in DBMS::dropDatabase" << endl;
		return false;
	}

	void showDatabase() {
		vector<string> dbs = getDatabases();

		for (int i = 0; i < dbs.size(); i++) {
			cout << dbs[i] << endl;

			vector<string> tables = getTables(dbs[i]);
			for (int j = 0; j < tables.size(); j++) {
				cout << "  " << tables[j] << endl;
			}
		}
	}

	void showTables() {
		if (existDatabase(dbName)) {
			vector<string> tables = getTables(dbName);
			for (int i = 0; i < tables.size(); i++) {
				cout << tables[i] << endl;
			}
		} else {
			cout << "Database not found error in DBMS::showTables" << endl;
		}
	}

	bool createTable(Table table) {
		for (int i = 0; i < tableManagers.size(); i++) {
			if (tableManagers[i].getTable().getName() == table.getName()) {
				cout << "Table already exist in DBMS::createTable" << endl;
				return false;
			}
		}
		TableManager tableManager(dbName);
		tableManager.createTable(table);
		tableManager.openTable(table.getName());
		tableManagers.push_back(tableManager);
		return true;
	}

	bool createTable(string _tableName, vector<ColumnDec*> _columnList, PrimaryKey* _primayKey) {
		Table table;
		table.setCreateStmt(_tableName, _columnList, _primayKey);
		return createTable(table);
	}

    bool createTable(string expression) {
        Table table;
        table.setExpression(expression);
        return createTable(table);
    }

	bool dropTable(string tableName) {
		for (vector<TableManager>::iterator iter = tableManagers.begin(); iter != tableManagers.end(); iter++) {
			if (iter->getTable().getName() == tableName) {
				iter->dropTable(tableName);
				tableManagers.erase(iter);
				return true;
			}
		}

		cout << "Table not found error in DBMS::dropTable" << endl;
		return false;
	}

	void descTable(string tableName) {
		for (int i = 0; i < tableManagers.size(); i++) {
			if (tableManagers[i].getTable().getName() == tableName) {
				tableManagers[i].getTable().output();
				return;
			}
		}

		cout << "Table not found error in DBMS::descTable" << endl;
	}

	void insertRecord(string tableName, string expression) {
    	for (int i = 0; i < tableManagers.size(); i++) {
			if (tableManagers[i].getTable().getName() == tableName) {
				Record record(tableManagers[i].getTable());
    			record.setExpression(expression);
    			tableManagers[i].insertRecord(record);
				return;
			}
		}

		cout << "Table not found error in DBMS::insertRecord" << endl;
	}

    void insertRecord(string tableName, vector<string> segment) {
        for(int i = 0; i < tableManagers.size(); i++) {
            if(tableManagers[i].getTable().getName() == tableName) {
                Record record(tableManagers[i].getTable());
                if (tableManagers[i].getTable().getPrimaryKey() == "rid") {
            		segment.push_back(Str::toString(tableManagers[i].getRecordNumber()));
        		}
                record.setAttrValue(segment);
                tableManagers[i].insertRecord(record);
                return;
            }
        }
    }

	void updateRecord(string tableName, Record record) {
    	for (int i = 0; i < tableManagers.size(); i++) {
			if (tableManagers[i].getTable().getName() == tableName) {
    			tableManagers[i].updateRecord(record);
				return;
			}
		}

		cout << "Table not found error in DBMS::updateRecord" << endl;
	}

	void deleteRecord(string tableName, string primaryValue) {
    	for (int i = 0; i < tableManagers.size(); i++) {
			if (tableManagers[i].getTable().getName() == tableName) {
    			tableManagers[i].deleteRecord(primaryValue);
				return;
			}
		}

		cout << "Table not found error in DBMS::deleteRecord" << endl;
	}

	vector<Record> getRecords(string tableName) {
		for (int i = 0; i < tableManagers.size(); i++) {
			if (tableManagers[i].getTable().getName() == tableName) {
                //cout << tableName << " ";
				return tableManagers[i].getRecords();
			}
		}

		cout << "Table " + tableName + " not found error in DBMS::getRecords" << endl;
		return vector<Record>();
	}

    vector<string> getSegments(string tableName){
        for(int i = 0; i < tableManagers.size(); i++) {
            if(tableManagers[i].getTable().getName() == tableName) {
                return tableManagers[i].getTable().getSegmentsString();
            }
        }
    }
};

#endif
